#include <clusnet/base/RingBuffer.h>
#include <gtest/gtest.h>

#include <vector>

using namespace clusnet::base;

template<typename T>
void checkRingBuffer(const RingBuffer<T>& vec1, std::initializer_list<T> vec2)
{
    ASSERT_EQ(vec1.size(), vec2.size());
    auto iter = vec2.begin();
    for (size_t i = 0; i < vec1.size(); ++i, ++iter)
    {
        ASSERT_EQ(vec1[i], *iter);
    }
}

class Object final
{
    public:
        Object()
        {
            g_count++;
        }
        ~Object()
        {
            g_count--;
        }
        Object(const Object&)
        {
            g_count++;
        }

        static int g_count;
};

int Object::g_count = 0;

TEST(RingBuffer, Test_construct)
{
    RingBuffer<int>     vec1(5);
    ASSERT_TRUE(vec1.empty());

    RingBuffer<int>     vec2(5, 3);
    ASSERT_EQ(vec2.size(), 5);
    ASSERT_EQ(vec2.capacity(), 5);
    checkRingBuffer(vec2, {3, 3, 3, 3, 3});

    {
        RingBuffer<Object>  tvec(6, Object());
        ASSERT_EQ(Object::g_count, 6);
    }
    ASSERT_EQ(Object::g_count, 0);
}

class RingBufferTest : public testing::Test
{
    public:
        RingBufferTest()
            : buffer_(5)
        {}
        ~RingBufferTest() = default;
        RingBufferTest(const RingBufferTest&) = delete;
        RingBufferTest& operator= (const RingBufferTest&) = delete;

        virtual void SetUp() override
        {
            ASSERT_TRUE(buffer_.empty());
        }

    protected:
        RingBuffer<int> buffer_;
};

TEST_F(RingBufferTest, Test_push)
{
    buffer_.push_back(1);
    buffer_.push_back(2);
    buffer_.emplace_back(3);
    buffer_.emplace_back(4);
    checkRingBuffer(buffer_, {1, 2, 3, 4});

    buffer_.push_front(6);
    checkRingBuffer(buffer_, {6, 1, 2, 3, 4});

    buffer_.push_back(7);
    checkRingBuffer(buffer_, {1, 2, 3, 4, 7});

    buffer_.push_front(8);
    checkRingBuffer(buffer_, {8, 1, 2, 3, 4});

    buffer_.emplace_back(9);
    checkRingBuffer(buffer_, {1, 2, 3, 4, 9});
}

TEST_F(RingBufferTest, Test_pop)
{
    buffer_.push_back(1);
    buffer_.push_back(2);
    buffer_.push_back(3);
    buffer_.push_back(4);
    buffer_.push_back(5);
    ASSERT_EQ(buffer_.back(), 5);
    ASSERT_EQ(buffer_.front(), 1);

    buffer_.pop_back();
    checkRingBuffer(buffer_, {1, 2, 3, 4});

    buffer_.pop_front();
    checkRingBuffer(buffer_, {2, 3, 4});

    auto iter = buffer_.begin();
    buffer_.erase(++iter);
    checkRingBuffer(buffer_, {2, 4});
}

TEST_F(RingBufferTest, Test_clear)
{
    buffer_.push_back(1);
    buffer_.push_back(2);
    buffer_.push_back(3);
    ASSERT_EQ(buffer_.capacity(), 5);
    ASSERT_EQ(buffer_.size(), 3);
    ASSERT_EQ(buffer_.reserve(), 2);

    buffer_.clear();
    ASSERT_EQ(buffer_.capacity(), 5);
    ASSERT_EQ(buffer_.reserve(), 5);
}

TEST_F(RingBufferTest, Test_setCapacity)
{
    buffer_.push_back(1);
    buffer_.push_back(2);
    buffer_.push_back(3);
    buffer_.set_capacity(8);
    ASSERT_EQ(buffer_.capacity(), 8);
    ASSERT_EQ(buffer_.size(), 3);

    buffer_.push_back(4);
    buffer_.push_back(5);
    buffer_.push_back(6);
    buffer_.push_back(7);
    checkRingBuffer(buffer_, {1, 2, 3, 4, 5, 6, 7});

    buffer_.set_capacity(3);
    ASSERT_EQ(buffer_.capacity(), 3);
    ASSERT_EQ(buffer_.size(), 3);
    checkRingBuffer(buffer_, {1, 2, 3});
}

TEST_F(RingBufferTest, Test_copyConstruct)
{
    buffer_.push_back(3);
    auto leftCopyBuffer(buffer_);
    ASSERT_EQ(leftCopyBuffer, buffer_);
    
    auto rightCopyBuffer(std::move(leftCopyBuffer));
    ASSERT_EQ(rightCopyBuffer, buffer_);
}

TEST_F(RingBufferTest, Test_assign)
{
    decltype(buffer_)   leftAssignBuffer(3);
    leftAssignBuffer = buffer_;
    ASSERT_EQ(leftAssignBuffer, buffer_);

    decltype(buffer_)   rightAssignBuffer(1);
    rightAssignBuffer = std::move(leftAssignBuffer);
    ASSERT_EQ(rightAssignBuffer, leftAssignBuffer);
}

TEST_F(RingBufferTest, Test_swap)
{
    decltype(buffer_)   tmpBuffer(3);
    tmpBuffer.push_back(1);
    tmpBuffer.push_back(2);

    auto bufferBak(buffer_);
    auto tmpBufferBak = tmpBuffer;
    swap(bufferBak, tmpBufferBak);
    ASSERT_EQ(buffer_, tmpBufferBak);
    ASSERT_EQ(tmpBuffer, bufferBak);
}

TEST_F(RingBufferTest, Test_iter)
{
    buffer_.push_back(1);
    buffer_.push_back(2);
    buffer_.push_back(3);
    ASSERT_EQ(buffer_.size(), 3);
    auto iter = buffer_.begin();
    *iter = 0;
    checkRingBuffer(buffer_, {0, 2, 3});

    *++iter = 1;
    checkRingBuffer(buffer_, {0, 1, 3});

    iter++;
    *iter = 2;
    checkRingBuffer(buffer_, {0, 1, 2});

    iter = buffer_.end();
    *--iter = 5;
    *--iter = 6;
    *--iter = 7;
    checkRingBuffer(buffer_, {7, 6, 5});

    iter = buffer_.end();
    iter--;
    *iter = 10;
    checkRingBuffer(buffer_, {7, 6, 10});

    std::vector<int> valid = {7, 6, 10};
    std::vector<int> res;
    for (auto& elem : buffer_)
    {
        res.push_back(elem);
    }
    ASSERT_EQ(valid, res);

    res.clear();
    for (auto it = buffer_.cbegin(); it != buffer_.cend(); ++it)
    {
        res.push_back(*it);
    }
    ASSERT_EQ(valid, res);
}

int main(int argc, char* argv[])
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
